/**
 * @description 下拉框基础组件
 * @date 2024.9.10
 * @author wz
 * @midify 2025.5.15
 * @editor wz
 */
import { CCFloat, CCInteger, instantiate, math, NodeEventType, tween, Tween, v2, v3, Vec3, Widget } from 'cc';
import { UITransform } from 'cc';
import { _decorator, Component, Enum, EventHandler, Node, ScrollView } from 'cc';
import { M_Direction, V_Direction, H_Direction, V_Corner, H_Corner } from '../common/Enum';
const { ccclass, property } = _decorator;


/**
 * 滚动列表循环复用item
 * 1:支持在编辑器绑定item脚本触发更新事件，缺点不能方便使用外部相关参数
 * 2:支持直接设置回调触发更新时间
 * warning: 两种方式二选一避免重复调用
 */
@ccclass('CLoopScroll')
export class CLoopScroll extends Component {
    @property({ type: ScrollView, tooltip: 'content节点anchor会根据具体滑动方向调整为(0.5,1),(0.5,0)(1,0.5),(0.5,0)' })
    scrollView: ScrollView;
    @property({ type: Node, tooltip: 'item节点anchor(0.5,0.5)' })
    item: Node;

    @property({ type: Enum(M_Direction), displayName: '主方向', tooltip: '竖屏 ,横屏' })
    m_direction: M_Direction = M_Direction.VERTICAL;
    @property({ type: Enum(V_Direction), displayName: '垂直方向', tooltip: '从上到下或从下到上', visible() { return this.m_direction == M_Direction.VERTICAL } })
    v_direction: V_Direction = V_Direction.TOP_TO_BOTTOM;
    @property({ type: Enum(H_Direction), displayName: '水平方向', tooltip: '从左到右或从右到左', visible() { return this.m_direction == M_Direction.HORIZONTAL } })
    h_direction: H_Direction = H_Direction.LEFT_TO_RIGHT;
    @property({ type: Enum(V_Corner), displayName: '垂直方向StartCorner', tooltip: '从左往右或从右往左', visible() { return this.m_direction == M_Direction.VERTICAL } })
    v_corner: V_Corner = V_Corner.START_LEFT_RIGHT;
    @property({ type: Enum(H_Corner), displayName: '水平方向StartCorner', tooltip: '从左往右或从右往左', visible() { return this.m_direction == M_Direction.HORIZONTAL } })
    h_corner: H_Corner = H_Corner.START_TOP_BOTTOM;

    @property({ type: CCInteger, displayName: '纵向布局时的列数', tooltip: '纵向布局时的列数', visible() { return this.m_direction == M_Direction.VERTICAL } })
    colNum: number = 1;
    @property({ type: CCInteger, displayName: '横向布局时的行数', tooltip: '横向布局时的行数', visible() { return this.m_direction == M_Direction.HORIZONTAL } })
    rowNum: number = 1;

    @property({ type: CCFloat, displayName: 'paddingLeft', tooltip: '左边界和节点内边距' })
    paddingLeft: number = 0;
    @property({ type: CCFloat, displayName: 'paddingRight', tooltip: '右边界和节点内边距' })
    paddingRight: number = 0;
    @property({ type: CCFloat, displayName: 'paddingTop', tooltip: '上边界和节点内边距' })
    paddingTop: number = 0;
    @property({ type: CCFloat, displayName: 'paddingBottom', tooltip: '下边界和节点内边距' })
    paddingBottom: number = 0;
    @property({ type: CCFloat, displayName: 'spacingY', tooltip: '纵向间隔' })
    spacingY: number = 0;
    @property({ type: CCFloat, displayName: 'spacingX', tooltip: '横向间隔' })
    spacingX: number = 0
    @property({ type: CCFloat, displayName: '刷新间隔时间', tooltip: '刷新间隔时间' })
    updateDelayTime: number = 0.1
    @property({ type: [EventHandler], tooltip: '刷新响应事件' })
    loopEvents: EventHandler[] = [];

    get itemWidth(): number {
        return this.item.getComponent(UITransform).width;
    };
    get itemHeight(): number {
        return this.item.getComponent(UITransform).height;
    };

    private _initialled: boolean = false;
    private _content: Node;
    private _itemCount: number = 0;
    private _updateTimer: number = 0;
    private _itemList: { index: number, node: Node, isEvent: boolean }[] = [];
    private _storageList: { index: number, node: Node, isEvent: boolean }[] = [];
    private _dataList: any[] = [];
    private _scrollTween: Tween<Node> = null;
    private _updateCallback: (item: Node, index: number, data: any) => void = null;


    protected onDestroy(): void {
        if (this._scrollTween)
            this._scrollTween.stop();
    }

    /**
     * 初始化设置
     */
    private init() {
        if (this._initialled) return;

        this.scrollView.node.on(NodeEventType.TOUCH_END, () => { console.log('-----TOUCH_END') }, this, true);
        this.scrollView.node.on(NodeEventType.TOUCH_CANCEL, () => { console.log('-----TOUCH_CANCEL') }, this, true);

        this._initialled = true;
        this.item.active = false
        this._content = this.scrollView.content;
        let scrollTrans = this.scrollView.getComponent(UITransform);
        if (this.m_direction == M_Direction.VERTICAL) {
            this.scrollView.horizontal = false;
            this.scrollView.vertical = true;

            let contentWith = this.paddingLeft + this.itemWidth * this.colNum + this.spacingX * (this.colNum - 1) + this.paddingRight;
            this._content.getComponent(UITransform).width = contentWith;
            let count = Math.ceil(scrollTrans.height / (this.itemHeight + this.spacingY)) + 2;
            this._itemCount = count * this.colNum;

            if (this.v_direction == V_Direction.TOP_TO_BOTTOM) {
                this._content.getComponent(UITransform).setAnchorPoint(0.5, 1);
                this._content.setPosition(v3(0, scrollTrans.height / 2))
            }
            else if (this.v_direction == V_Direction.BOTTOM_TO_TOP) {
                this._content.getComponent(UITransform).setAnchorPoint(0.5, 0);
                this._content.setPosition(v3(0, -scrollTrans.height / 2))
            }
        }
        else if (this.m_direction == M_Direction.HORIZONTAL) {
            this.scrollView.horizontal = true;
            this.scrollView.vertical = false;

            let contentHeight = this.paddingTop + this.itemHeight * this.rowNum + this.spacingY * (this.rowNum - 1) + this.paddingBottom;
            this._content.getComponent(UITransform).height = contentHeight
            let count = Math.ceil(scrollTrans.width / (this.itemWidth + this.spacingX)) + 2;
            this._itemCount = count * this.rowNum;

            if (this.h_direction == H_Direction.LEFT_TO_RIGHT) {
                this._content.getComponent(UITransform).setAnchorPoint(0, 0.5);
                this._content.setPosition(v3(-scrollTrans.width / 2, 0))
            }
            else if (this.h_direction == H_Direction.RIGHT_TO_LEFT) {
                this._content.getComponent(UITransform).setAnchorPoint(1, 0.5);
                this._content.setPosition(v3(scrollTrans.width / 2, 0))
            }
        }
    }

    /**
     * 提前计算所需信息
     */
    private caculateLength(dataLength: number) {
        if (this.m_direction == M_Direction.VERTICAL) {
            const count = Math.ceil(dataLength / this.colNum);
            this._content.getComponent(UITransform).height = this.paddingBottom + this.paddingTop + count * this.itemHeight + Math.max(0, (count - 1)) * this.spacingY
        }
        else if (this.m_direction == M_Direction.HORIZONTAL) {
            const count = Math.ceil(dataLength / this.rowNum);
            this._content.getComponent(UITransform).width = this.paddingLeft + this.paddingRight + count * this.itemWidth + Math.max(0, (count - 1)) * this.spacingX
        }
    }

    /**
     * 计算并生成item
     */
    private generateItem(delayTime: number) {
        if (this._scrollTween)
            this._scrollTween.stop();

        //打开刷新
        this._itemList.forEach(e => e.isEvent = true)

        const needCount = this._itemCount - this._itemList.length;
        if (needCount > 0) {
            let getOneItem = () => {
                this._itemList.sort((a, b) => a.index - b.index)
                let maxIndex = this._itemList.length <= 0 ? -1 : this._itemList[this._itemList.length - 1].index
                if (this._storageList.length > 0) {
                    this._storageList[0].index = maxIndex + 1;
                    this._storageList[0].node.active = true;
                    this._storageList[0].isEvent = true;
                    this._itemList.push(this._storageList[0]);
                    this._storageList.splice(0, 1);
                }
                else {
                    const tempItem: Node = instantiate(this.item);
                    tempItem.parent = this.item.parent;
                    tempItem.active = true;
                    this._itemList.push({ index: maxIndex + 1, node: tempItem, isEvent: true });
                }
            }

            if (delayTime <= 0) {
                for (let i = 0; i < needCount; i++)
                    getOneItem();
            }
            else {
                this._scrollTween = tween(this.node);
                for (let i = 0; i < needCount; i++) {
                    this._scrollTween
                        .delay(i == 0 ? 0 : delayTime)
                        .call(() => {
                            getOneItem();
                            this.updateItem();
                        })
                }
                this._scrollTween.union().start()
            }
        }
        else if (needCount < 0) {
            this._itemList.sort((a, b) => a.index - b.index)
            while (this._itemList.length > -needCount) {
                let item = this._itemList.shift()
                item.node.active = false;
                this._storageList.push(item);
            }
        }
    }


    /**
     * 发射事件
     */
    private emitItem(item: { index: number, node: Node, isEvent: boolean }) {
        if (item.node.active && item.isEvent) {
            this.loopEvents.forEach((a) => {
                if (a.component != '' || a._componentName) {
                    let script: any = item.node.getComponent(a._componentName);
                    //绑在item上的事件替换下触发节点
                    if (script)
                        a.target = item.node
                    a.emit([item.node, item.index, this._dataList[item.index]])
                }
            })

            if (this._updateCallback)
                this._updateCallback(item.node, item.index, this._dataList[item.index]);
        }
        item.isEvent = false;
    }

    /**
     * 检查item显示边界
     */
    private checkItemIndex(startIndex: number, endIndex: number) {
        if (this._itemList.length > 0) {
            this._itemList.sort((a, b) => a.index - b.index)

            let showStartIndex = this._itemList[0].index;
            let showEndIndex = this._itemList[this._itemList.length - 1].index

            //有重复(未知的异常情况,暂未发现此种情况)
            if ((showEndIndex - showStartIndex + 1) != this._itemList.length) {
                console.error('有重复index:', this._itemList.map(a => a.index))
                for (let i = 0; i < this._itemList.length; i++) {
                    this._itemList[i].index = i + startIndex;
                    this._itemList[i].isEvent = true;
                }
            }
            //无交集
            else if (showStartIndex > endIndex || showEndIndex < startIndex) {
                for (let i = 0; i < this._itemList.length; i++) {
                    this._itemList[i].index = i + startIndex;
                    this._itemList[i].isEvent = true;
                }
            }
            //有交集
            else if (showStartIndex < startIndex) {
                for (let i = 0; i < this._itemList.length; i++) {
                    if (this._itemList[i].index < startIndex) {
                        this._itemList[i].index = ++showEndIndex;
                        this._itemList[i].isEvent = true;
                    }
                }
            }
            //有交集
            else if (showStartIndex > startIndex) {
                for (let i = 0; i < this._itemList.length; i++) {
                    if (this._itemList[i].index > endIndex) {
                        this._itemList[i].index = --showStartIndex;
                        this._itemList[i].isEvent = true;
                    }
                }
            }
        }
    }

    private update_top_to_bottom() {
        if (this.m_direction == M_Direction.VERTICAL && this.v_direction == V_Direction.TOP_TO_BOTTOM) {
            let startIndex = 0, endIndex = 0;
            let scrollOffsetY = this._content.position.y - this.scrollView.node.getComponent(UITransform).height / 2;
            let startRow = scrollOffsetY < this.paddingTop ? 0 : Math.floor((scrollOffsetY - this.paddingTop) / (this.itemHeight + this.spacingY))
            startIndex = startRow * this.colNum;
            endIndex = startIndex + this._itemList.length - 1;

            //检查边界
            this.checkItemIndex(startIndex, endIndex);

            //设置位置
            const contenTrans = this._content.getComponent(UITransform);
            for (let i = 0; i < this._itemList.length; i++) {
                let index = this._itemList[i].index;
                let node = this._itemList[i].node;
                let rowIndex = Math.floor(index / this.colNum);
                let colIndex = index % this.colNum;
                let posX, posY;
                if (this.v_corner == V_Corner.START_LEFT_RIGHT)
                    posX = -contenTrans.width / 2 + this.paddingLeft + (colIndex + 0.5) * this.itemWidth + colIndex * this.spacingX;
                else
                    posX = contenTrans.width / 2 - this.paddingRight - (colIndex + 0.5) * this.itemWidth - colIndex * this.spacingX;
                posY = -(this.paddingTop + (rowIndex + 0.5) * this.itemHeight + rowIndex * this.spacingY);
                node.setPosition(posX, posY);
                //超界隐藏
                node.active = index >= 0 && index < this._dataList.length;
                this.emitItem(this._itemList[i])
            }
        }
    }

    private update_bottom_to_top() {
        if (this.m_direction == M_Direction.VERTICAL && this.v_direction == V_Direction.BOTTOM_TO_TOP) {
            let startIndex = 0, endIndex = 0;
            let scrollOffsetY = - this.scrollView.getComponent(UITransform).height / 2 - this._content.position.y;
            let startRow = scrollOffsetY < this.paddingBottom ? 0 : Math.floor((scrollOffsetY - this.paddingBottom) / (this.itemHeight + this.spacingY))
            startIndex = startRow * this.colNum;
            endIndex = startIndex + this._itemList.length - 1;

            //检查边界
            this.checkItemIndex(startIndex, endIndex);

            //设置位置
            const contenTrans = this._content.getComponent(UITransform);
            for (let i = 0; i < this._itemList.length; i++) {
                let index = this._itemList[i].index;
                let node = this._itemList[i].node;
                let rowIndex = Math.floor(index / this.colNum);
                let colIndex = index % this.colNum;
                let posX, posY;
                if (this.v_corner == V_Corner.START_LEFT_RIGHT)
                    posX = -contenTrans.width / 2 + this.paddingLeft + (colIndex + 0.5) * this.itemWidth + colIndex * this.spacingX;
                else
                    posX = contenTrans.width / 2 - this.paddingRight - (colIndex + 0.5) * this.itemWidth - colIndex * this.spacingX;
                posY = this.paddingBottom + (rowIndex + 0.5) * this.itemHeight + rowIndex * this.spacingY;
                node.setPosition(posX, posY);
                //超界隐藏
                node.active = index >= 0 && index < this._dataList.length;
                this.emitItem(this._itemList[i])
            }
        }
    }

    private update_left_to_right() {
        if (this.m_direction == M_Direction.HORIZONTAL && this.h_direction == H_Direction.LEFT_TO_RIGHT) {
            let startIndex = 0, endIndex = 0;
            let scrollOffsetX = - this.scrollView.getComponent(UITransform).width / 2 - this._content.position.x;
            let startRow = scrollOffsetX < this.paddingLeft ? 0 : Math.floor((scrollOffsetX - this.paddingLeft) / (this.itemWidth + this.spacingX))
            startIndex = startRow * this.rowNum;
            endIndex = startIndex + this._itemList.length - 1;

            //检查边界
            this.checkItemIndex(startIndex, endIndex);

            //设置位置
            const contenTrans = this._content.getComponent(UITransform);
            for (let i = 0; i < this._itemList.length; i++) {
                let index = this._itemList[i].index;
                let node = this._itemList[i].node;
                let rowIndex = index % this.rowNum;
                let colIndex = Math.floor(index / this.rowNum);
                let posX, posY;
                posX = this.paddingLeft + (colIndex + 0.5) * this.itemWidth + colIndex * this.spacingX;
                if (this.h_corner == H_Corner.START_TOP_BOTTOM)
                    posY = contenTrans.height / 2 - this.paddingTop - (rowIndex + 0.5) * this.itemHeight - rowIndex * this.spacingY;
                else
                    posY = -contenTrans.height / 2 + this.paddingBottom + (rowIndex + 0.5) * this.itemHeight + rowIndex * this.spacingY;
                node.setPosition(posX, posY);
                //超界隐藏
                node.active = index >= 0 && index < this._dataList.length;
                this.emitItem(this._itemList[i])
            }
        }
    }

    private update_right_to_left() {
        if (this.m_direction == M_Direction.HORIZONTAL && this.h_direction == H_Direction.RIGHT_TO_LEFT) {
            let startIndex = 0, endIndex = 0;
            let scrollOffsetX = this._content.position.x - this.scrollView.getComponent(UITransform).width / 2;
            let startRow = scrollOffsetX < this.paddingLeft ? 0 : Math.floor((scrollOffsetX - this.paddingLeft) / (this.itemWidth + this.spacingX))
            startIndex = startRow * this.rowNum;
            endIndex = startIndex + this._itemList.length - 1;

            //检查边界
            this.checkItemIndex(startIndex, endIndex);

            //设置位置
            const contenTrans = this._content.getComponent(UITransform);
            for (let i = 0; i < this._itemList.length; i++) {
                let index = this._itemList[i].index;
                let node = this._itemList[i].node;
                let rowIndex = index % this.rowNum;
                let colIndex = Math.floor(index / this.rowNum);
                let posX, posY;
                posX = -(this.paddingRight + (colIndex + 0.5) * this.itemWidth + colIndex * this.spacingX);
                if (this.h_corner == H_Corner.START_TOP_BOTTOM)
                    posY = contenTrans.height / 2 - this.paddingTop - (rowIndex + 0.5) * this.itemHeight - rowIndex * this.spacingY;
                else
                    posY = -contenTrans.height / 2 + this.paddingBottom + (rowIndex + 0.5) * this.itemHeight + rowIndex * this.spacingY;
                node.setPosition(posX, posY);
                //超界隐藏
                node.active = index >= 0 && index < this._dataList.length;
                this.emitItem(this._itemList[i])
            }

        }
    }

    updateItem() {
        if (this.m_direction == M_Direction.VERTICAL) {
            if (this.v_direction == V_Direction.TOP_TO_BOTTOM)
                this.update_top_to_bottom();
            else if (this.v_direction == V_Direction.BOTTOM_TO_TOP)
                this.update_bottom_to_top()
        }
        else if (this.m_direction == M_Direction.HORIZONTAL) {
            if (this.h_direction == H_Direction.LEFT_TO_RIGHT)
                this.update_left_to_right();
            else if (this.h_direction == H_Direction.RIGHT_TO_LEFT)
                this.update_right_to_left()
        }
    }

    update(dt) {
        if (!this._initialled) {
            return;
        }
        this._updateTimer += dt;
        if (this._updateTimer < this.updateDelayTime) {
            return; // 我们不需要每帧都做计算
        }
        this._updateTimer = 0;
        this.updateItem();
    }

    /**
     * 获取列表数据
     */
    getDataList() {
        return this._dataList;
    }


    /**
     * 获取节点
     */
    getValidItemInfo() {
        return this._itemList.filter(a => a.node.active)
    }

    /**
     * 重置
     */
    reset() {
        if (this._scrollTween)
            this._scrollTween.stop();

        while (this._itemList.length > 0) {
            let item = this._itemList.shift();
            item.node.active = false;
            this._storageList.push(item);
        }

        this._initialled = false;
        this._itemCount = 0;
        this._dataList.length = 0;
    }

    /**
     * 设置更新item的回调
     * @param callback 
     */
    setUpdateItemCallback(callback: (item: Node, index: number, data: any) => void) {
        this._updateCallback = callback
    }

    /**
     * 刷新数据显示
     * @param dataList 显示的数据
     * @param delayTime 延迟显示item时间 
     */
    updateData(dataList: any[], delayTime: number = 0) {
        this.init();

        if (this._dataList.length != dataList.length)
            this.caculateLength(dataList.length);

        this._dataList = dataList;
        this.generateItem(delayTime);
        this.updateItem();
    }

    /**
     * 滑动到指定的index
     * @param index 对应的index
     * @param offset 对应index的偏移
     * @param scrollTime 
     * @returns 
     */
    scrollToIndex(index: number, offset: number = 0, scrollTime: number = 0.1) {
        this.scrollView.getComponent(Widget)?.updateAlignment();
        let viewHeight = this.scrollView.getComponent(UITransform).height;
        let viewWidth = this.scrollView.getComponent(UITransform).width;
        let contentHeight = this.scrollView.content.getComponent(UITransform).height;
        let contentWidth = this.scrollView.content.getComponent(UITransform).width;
        let scrollHeight = 0;
        if (this.m_direction == M_Direction.VERTICAL) {
            scrollHeight = contentHeight - viewHeight;
            if (scrollHeight <= 0) return;
        }
        let scrollWidth = 0;
        if (this.m_direction == M_Direction.HORIZONTAL) {
            scrollWidth = contentWidth - viewWidth;
            if (scrollWidth <= 0) return;
        }

        const offset_index = index + (offset > 0 ? offset : 0);

        if (this.m_direction == M_Direction.VERTICAL) {
            let row = Math.ceil((offset_index + 1) / this.colNum) - 1
            if (this.v_direction == V_Direction.TOP_TO_BOTTOM) {
                let movePos = this.paddingTop + (row + 0.5) * this.itemHeight + row * this.spacingY - viewHeight;
                this.scrollView.scrollToPercentVertical(1 - math.clamp01(movePos / scrollHeight), scrollTime, false);
            }
            else if (this.v_direction == V_Direction.BOTTOM_TO_TOP) {
                let movePos = this.paddingBottom + (row + 0.5) * this.itemHeight + row * this.spacingY - viewHeight;
                this.scrollView.scrollToPercentVertical(math.clamp01(movePos / scrollHeight), scrollTime, false);
            }
        }
        else if (this.m_direction == M_Direction.HORIZONTAL) {
            let col = Math.ceil((offset_index + 1) / this.rowNum) - 1
            if (this.h_direction == H_Direction.LEFT_TO_RIGHT) {
                let movePos = this.paddingLeft + (col + 0.5) * this.itemWidth + col * this.spacingX - viewWidth;
                this.scrollView.scrollToPercentHorizontal(math.clamp01(movePos / scrollWidth), scrollTime, false);
            }
            else if (this.h_direction == H_Direction.RIGHT_TO_LEFT) {
                let movePos = this.paddingRight + (col + 0.5) * this.itemWidth + col * this.spacingX - viewWidth;
                this.scrollView.scrollToPercentHorizontal(1 - math.clamp01(movePos / scrollWidth), scrollTime, false);
            }
        }
    }
}


